package cn.blu10ph.trustshare.core;

import cn.blu10ph.trustshare.bean.msg.BaseMsg;
import cn.blu10ph.trustshare.bean.msg.TrustMsg;
import cn.blu10ph.trustshare.bean.node.GetPublicKeyMsg;
import cn.blu10ph.trustshare.bean.node.NodeInfo;
import cn.blu10ph.trustshare.bean.node.TrustNode;
import cn.blu10ph.trustshare.config.ServiceConfig;
import cn.blu10ph.trustshare.constant.Constant;
import cn.blu10ph.trustshare.service.DHTMsgService;
import cn.blu10ph.trustshare.service.impl.DHTMsgBaseService;
import cn.blu10ph.trustshare.util.HashAndSignUtil;
import cn.blu10ph.trustshare.util.PgpUtil;
import io.netty.channel.ChannelFuture;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p> ServiceCore </p >
 *
 * @author cxx
 * @date 2024/4/1 22:22
 */
@Slf4j
@Component
public class ServiceCore {

    @Resource
    RoutingTable routingTable;

    @Resource
    PGPManager pgpManager;

    protected static ServiceConfig config;

    protected static final Map<String, DHTMsgService> msgService = new HashMap<>();

    protected static final Map<String, Map<String, BaseMsg>> waitMsgMap = new HashMap<>();

    // init

    @Resource
    public void setConfig(ServiceConfig config) {
        ServiceCore.config = config;
    }

    @Resource
    public void initMsgService(List<DHTMsgService> msgServiceList) {
        if(!ObjectUtils.isEmpty(msgServiceList)){
            msgServiceList.forEach(item->{
                ServiceCore.msgService.put(item.getType(), item);
            });
        }
    }

    public static void initService(NodeInfo self, ChannelFuture channel) throws Exception {
        PGPSecretKey secretKey = null;
        if(StringUtils.hasText(config.getSecretKey())){
            try {
                secretKey = PgpUtil.loadSecretKey(config.getSecretKey());
            } catch (Exception ex) {
                ex.printStackTrace();
                throw new RuntimeException(ex);
            }
        } else {
            try {
                secretKey = PgpUtil.createSecretKey(config.getName(), config.getPassword(), 2048, null);
            } catch (Exception ex) {
                ex.printStackTrace();
                throw new RuntimeException(ex);
            }
        }
        String fingerprintStr = PGPManager.setSecretKey(secretKey);
        self.setNodeId(fingerprintStr);
        TrustNode selfSign = new TrustNode(self);

        // sign
        selfSign.setTime(System.currentTimeMillis());
        selfSign.setNonce(HashAndSignUtil.getNonce(selfSign.getTime()));
        String signHash = HashAndSignUtil.getNodeHash(selfSign);
        String sign = PgpUtil.createSignature(signHash, PGPManager.getSecretKey(), config.getPassword());
        selfSign.setSign(sign);

        RoutingTable.setSelfNode(selfSign);
        DHTMsgBaseService.setChannel(channel);
    }

    // node

    public TrustNode getSelfNode(){
        return routingTable.getSelfNode();
    }

    public List<TrustNode> getAllNodeList() {
        return routingTable.getAllNodeList();
    }

    public List<TrustNode> findNodeListById(String nodeId) {
        return routingTable.findNodeListById(nodeId);
    }

    public TrustNode getNode(String nodeId) {
        return routingTable.getNodeById(nodeId);
    }

    public void removeNode(String nodeId) {
        routingTable.removeNode(nodeId);
    }

    public Long getNodeActive(String nodeId) {
        return routingTable.getNodeActive(nodeId);
    }

    public void setNodeActive(String nodeId) {
        routingTable.setNodeActive(nodeId);
    }

    // PGP

    public PGPPublicKey getPublicKey(String publicKeyFingerprint){
        return pgpManager.getPublicKey(publicKeyFingerprint);
    }

    public boolean checkMsgSign(TrustMsg msg){
        if(!RoutingTable.self.getNodeId().equals(msg.getTo())){
            return false;
        }

        if(Constant.MSG_TYPE_GET_PUBLIC_KEY.equals(msg.getType())) {
            return true;
        }

        PGPPublicKey publicKey = getPublicKey(msg.getFrom());
        if(ObjectUtils.isEmpty(publicKey)){
            log.info("get from publicKey error:{}", msg.getFrom());
            return false;
        }

        return HashAndSignUtil.checkMsgSign(msg, publicKey);
    }

    // msg service
    public DHTMsgService getMsgService(String type) {
        return msgService.get(type);
    }

    public void sendGetPublicKey(NodeInfo nodeInfo) throws Exception {
        DHTMsgService<Object> msgService = this.getMsgService(Constant.MSG_TYPE_GET_PUBLIC_KEY);
        TrustNode node = new TrustNode(nodeInfo);
        msgService.sendQuery(node, new GetPublicKeyMsg(
                nodeInfo.getNodeId(), routingTable.getSelfNode(),
                PgpUtil.getPublicKeyEncode(PgpUtil.getPublicKey(PGPManager.getSecretKey()))
        ));
    }

    public void checkAndSendWaitMsg(TrustNode fromNode, TrustMsg msgObj){
        if(ObjectUtils.isEmpty(fromNode)){
            return;
        }

        // get wait msg
        BaseMsg baseMsg = this.getWaitMsg(msgObj.getType(), fromNode.getNodeId());
        if(ObjectUtils.isEmpty(baseMsg)){
            return;
        }

        // send wait msg
        DHTMsgService msgServiceTemp = this.getMsgService(baseMsg.getType());
        try {
            Object msgParams = msgServiceTemp.getParams(baseMsg.getParams());
            msgServiceTemp.sendQuery(fromNode, msgParams);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public BaseMsg getWaitMsg(String type, String nodeId){
        synchronized (waitMsgMap) {
            Map<String, BaseMsg> waitMsgs = waitMsgMap.get(type);
            if (ObjectUtils.isEmpty(waitMsgs)) {
                return null;
            } else {
                return waitMsgs.get(nodeId);
            }
        }
    }

    public void setWaitMsg(String type, String nodeId, BaseMsg msg){
        synchronized (waitMsgMap) {
            Map<String, BaseMsg> waitMsgs = waitMsgMap.get(type);
            if (ObjectUtils.isEmpty(waitMsgs)) {
                waitMsgs = new HashMap<>();
            }
            waitMsgs.put(nodeId, msg);
            waitMsgMap.put(type,waitMsgs);
        }
    }

}
